// Writed by Romiro27 (Ivan Rekunov)

#include <fstream>
#include <vector>
#include <array>

#ifdef __linux__
	#include <thread>
#elif _WIN32
	#include <ame/Win32/thread>
#endif

#ifdef __linux__
	#include <mutex>
#elif _WIN32
	#include <ame/Win32/mutex>
#endif

#ifndef _LOGGER_H
#define _LOGGER_H

namespace Logs {
    class FileSync
    {
        public:
            FileSync(const std::string& file_name, std::size_t buffer_size = 128u) : m_buffer(buffer_size), m_output(file_name), m_offset(0u)
            {
                if(!m_output.is_open())
                {
                    throw std::runtime_error("LOGGER_ERROR: Log file wasn't created!");
                }
            }

            void write(const std::string& data)
            {
                const std::size_t data_size = data.size();

                std::lock_guard<std::mutex> lock(m_mutex);

                if(data_size + m_offset >= m_buffer.size())
                {
                    record();
                }

                std::copy(begin(data), end(data), begin(m_buffer) + m_offset);

                m_offset += data_size;
            }

            ~FileSync()
            {
                record();
            }

        private:
            void record()
            {
                if(m_offset != 0u)
                {
                    m_output.write(m_buffer.data(), m_offset);
                    m_offset = 0u;
                }
            }

        private:
            std::ofstream m_output;
            std::vector<char> m_buffer;
            std::size_t m_offset;
            std::mutex m_mutex;
    };

    class Logger
    {
        public:
            enum Level
            {
                TRACE_LEVEL,
                DEBUG_LEVEL,
                INFO_LEVEL,
                WARN_LEVEL,
                ERROR_LEVEL,
                FATAL_LEVEL
            };

            static inline std::array<std::string, Level::FATAL_LEVEL + 1u> LevelStr =
            {
                "TRACE", "DEBUG",
                "INFO",  "WARNING",
                "ERROR", "FATAL"
            };

        public:
            int count = 0;

            Logger(const std::string& file_name) : m_sync(file_name)
            {

            }

            void log(Level level, const char* source, std::string message)
            {	count++;
                const std::string formated_message = std::to_string(count) + " [" + LevelStr[level] + "] - " + source + " - " + message + "\n";
                m_sync.write(formated_message);
            }

        private:
            FileSync m_sync;

    };
}

inline Logs::Logger& getGlobalLogger()
{
    static Logs::Logger logger("./data.log");

    return logger;
}

#define STR_(x) #x
#define STR(x) STR_(x)

#define LOG_TRACE(message) getGlobalLogger().log(Logs::Logger::Level::TRACE_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_DEBUG(message) getGlobalLogger().log(Logs::Logger::Level::DEBUG_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_INFO(message) getGlobalLogger().log(Logs::Logger::Level::INFO_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_WARN(message) getGlobalLogger().log(Logs::Logger::Level::WARN_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_ERROR(message) getGlobalLogger().log(Logs::Logger::Level::ERROR_LEVEL, __FILE__ ":" STR(__LINE__), (message))
#define LOG_FATAL(message) getGlobalLogger().log(Logs::Logger::Level::FATAL_LEVEL, __FILE__ ":" STR(__LINE__), (message))

#endif // LOGGER_H
